Skip to content

newsubgid: add deny_setgroups option to /etc/subgid#99

Open
cyphar wants to merge 5 commits intoshadow-maint:masterfrom
cyphar:subgid-flag-deny_setgroups
Open

newsubgid: add deny_setgroups option to /etc/subgid#99
cyphar wants to merge 5 commits intoshadow-maint:masterfrom
cyphar:subgid-flag-deny_setgroups

Conversation

@cyphar
Copy link
Contributor

@cyphar cyphar commented Feb 18, 2018

Add a new deny_setgroups (and corresponding allow_setgroups) flag to
/etc/subgid. The purpose of this flag is to extend the security
protections against CVE-2018-7169, so that even group mapping configured
in /etc/subgid by an administrator can still disable setgroups.

However, rather than the fairly lenient semantics for self-mapping, the
semantics of /etc/subgid are stronger. If a mapping is encountered where
"deny_setgroups" is set, then no other mapping can "undo" this
restriction. The reason for this is that "deny_setgroups" indicates that
(according to the administrator) the mapping is unsafe to allow setgroups
in, and adding more mappings will not change this fact. "allow_setgroups"
is the default, and setting it is a noop. The logic used when applying
setgroups policies is unchanged (only denies are written, and we don't
write anything if it's already denied).

Signed-off-by: Aleksa Sarai asarai@suse.de

@cyphar cyphar force-pushed the subgid-flag-deny_setgroups branch 2 times, most recently from bbdc6cf to f6f6335 Compare February 18, 2018 20:07
@cyphar
Copy link
Contributor Author

cyphar commented Feb 18, 2018

This is currently still work-in-progress (even though it all works fine now). The issue is that I cannot get lib/ to use libmisc/list.c because automake really doesn't like me trying to add an LDADD for libmisc.la to lib_la_LDADD. So currently I just copied list.c to lib/ (which is ... bad).

* Free a list.
* The output list isn't modified, but the memory is freed.
*/
void free_list (char *const *list)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: const char **list ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this to match the signature of dup_list, but I don't really mind either way.

src/newgidmap.c Outdated
*/
const char *Prog;

enum quadopt_t {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is this "quad"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the term quadoption from Mutt, but you're right that this might not be the best name.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this looks triopt, as it has three values.

In github.com/moby/buildkit we call this BoolOrAuto.

Copy link
Contributor Author

@cyphar cyphar Feb 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it has three values, I was referring to "quadoption" as a term in Mutt (see §26.1 of the Mutt manual). But maybe defaultbool_t is a better name. I'll fix it once I can fix the lib/ build issues.


if (strlen(flag) < 1)
continue;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment about that no uid flag is valid atm

Copy link

@AkihiroSuda AkihiroSuda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you update man pages as well

@cyphar
Copy link
Contributor Author

cyphar commented Feb 19, 2018

@AkihiroSuda Working on it, I'm trying to figure out how to fix AutoMake first.

@cyphar cyphar changed the title newsubgid: add deny_setgroups flag to /etc/subgid newsubgid: add deny_setgroups option to /etc/subgid Feb 19, 2018
@cyphar cyphar force-pushed the subgid-flag-deny_setgroups branch from f6f6335 to 35c6e43 Compare February 19, 2018 17:10
@cyphar
Copy link
Contributor Author

cyphar commented Feb 19, 2018

Alright the AutoMake issue has been resolved, so I've removed lib/list.c. I've also now included manpage updates for this change as well as #97 (to clarify when and how newgidmap will write deny), as well as changed quadoption to autobool (and added helpers for autobool_t).

There aren't any newgidmap tests, but I have a feeling we should probably add some (though the tests aren't run as part of Travis -- restricting their usefulness).

@cyphar cyphar force-pushed the subgid-flag-deny_setgroups branch from 35c6e43 to 8603235 Compare February 19, 2018 18:33
Add a free_list helper so that we can duplicate list to callers and
require them to do the freeing for us, as well as a comma_from_list
helper for the /etc/sub{uid,gid} writing functions. In addition, fix up
the duplication code to just ignore NULL lists rather than aborting (for
no good reason).

As an aside, we really should switch to sharing the list code from
somewhere else rather than maintaining a separate version within
shadow-utils...

Signed-off-by: Aleksa Sarai <asarai@suse.de>
Add support for an optional options field in /etc/sub{uid,gid}. We treat
this like other optional fields in /etc/passwd -- by ignoring its
non-existence and providing some functions to access the options.

We need these in order to be able to have the "allow_setgroups" and
"deny_setgroups" options in /etc/sub{uid,gid}.

This also required making libmisc a dependency of libshadow, which in
turn required converting libmisc to a libtool library so that AutoMake
didn't complain. It appears this mostly doesn't change any aspect of the
build other than allowing us to use libmisc symbols in libshadow.

Signed-off-by: Aleksa Sarai <asarai@suse.de>
Add the most basic support for the new /etc/sub{uid,gid} options
possible (parse them and if any options are present then output an
error). We ignore empty-string options to avoid cases where an empty
field breaks things.

Signed-off-by: Aleksa Sarai <asarai@suse.de>
Add a new deny_setgroups (and corresponding allow_setgroups) option to
/etc/subgid. The purpose of this option is to extend the security
protections against CVE-2018-7169, so that even group mapping configured
in /etc/subgid by an administrator can still disable setgroups.

However, rather than the fairly lenient semantics for self-mapping, the
semantics of /etc/subgid are stronger. If a mapping is encountered where
"deny_setgroups" is set, then no other mapping can "undo" this
restriction. The reason for this is that "deny_setgroups" indicates that
(according to the administrator) the mapping is unsafe to allow
setgroups in, and adding more mappings will not change this fact.
"allow_setgroups" is the default, and setting it is a noop. The logic
used when applying setgroups policies is unchanged (only denies are
written, and we don't write anything if it's already denied).

Signed-off-by: Aleksa Sarai <asarai@suse.de>
Add documentation for allow_setgroups, deny_setgroups, the new option
format of /etc/sub{uid,gid}, and fix some errors in the groupmod(8) man
page that stopped it from building properly on my machine.

Signed-off-by: Aleksa Sarai <asarai@suse.de>
@cyphar cyphar force-pushed the subgid-flag-deny_setgroups branch from 8603235 to 626af91 Compare February 19, 2018 18:38
@hallyn
Copy link
Member

hallyn commented Feb 19, 2018 via email

@cyphar
Copy link
Contributor Author

cyphar commented Feb 19, 2018

Oh, I also haven't added any way for people to set deny_setgroups (or other options in the future) within usermod or useradd -- I couldn't figure out what the interface should look like. Any suggestions, or should we leave that feature for later?

for (i = 0; NULL != list[i]; i++)
commalen += strlen(list[i]) + 1;

comma = xmalloc(commalen + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically +1 shouldn't be needed here right? Each entry is followed by either comma or \0, and you add +1 to the length of each entry as you add up.

Copy link
Collaborator

@alejandro-colomar alejandro-colomar Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, the loops are simpler if we leave a trailing comma, so that a CSV would look like a,b,c,\0. It is technically incorrect as a CSV --as you have a trailing empty element--, but we do support such CSVs elsewhere, so we could keep the +1.

memset(comma, '\0', commalen + 1);

for (i = 0; NULL != list[i]; i++) {
int j = strlen(comma);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really performance critical, but re-calculating j at each iteration here is unnecessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but you're using strncat which doesn't give you the printed length, I see :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now have stpecpy(), which is designed precisely with this in mind.

range->owner,
range->start,
range->count,
options ?: "");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc this is a gnu extension, probably not safe for shadow to use, so please make it options ? options : ""

Copy link
Collaborator

@alejandro-colomar alejandro-colomar Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have recently embraced this GNU extension.


<variablelist>
<varlistentry>
<term><option>allow_setgroups</option> (default),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, is this the right place to put this kind of thing though? Maybe it is, but:

The only case I know of for deny-setgroups is when an administrator has placed a user in a group with a negative acl, to prevent that user from accessing some resource.

In that case, does it make more sense to place a sort of "group_locked" option on the group itself in /etc/group? Then when newgidmap runs, it checks whether the target pid is in any locked group, and if so sets deny_setgroup.

Placing the deny/allow_setgroup variable in the gid mapping seems a step removed, and might cause an admin to not notice that "hey, the user being allowd to grab those gids should have been locked into group nos3critdocs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I hadn't though of this point.

You're quite right that most users of negative ACLs are done with groups (and I think it does make intuitive sense to be able to say "if you are in a bad group then you cannot use setgroups"), but I have a couple of questions about edge cases:

  • Should an administrator be able to override this decision for particular users or groups? Should that also be in /etc/groups (or /etc/passwd)?

  • What if it's a numeric group (so not one registered in /etc/groups)? Is the solution to just tell people "tough luck, you should register your group"?

  • This is a bit of an edge condition (and is an edge condition that actually isn't really easy to handle with the current implementation either), but how will this affect containers on the system that are also using group blacklisting? While /etc/sub* are global, if you have containers using allocated portions of the UID/GID space then you would expect that a user shouldn't be able to use newgidmap to drop the negative ACLs for a different group set than the one you are joining? Now, this is quite a weird case because it would require you to have a quite strange ACL setup -- so maybe this is a use case we don't care about?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should an administrator be able to override this decision for particular users or groups? Should that also be in /etc/groups (or /etc/passwd)?

I don't really think so, as you either want to deny the group certain access or you don't, but my gut says there are more subtle cases. So let's say yes.

What if it's a numeric group (so not one registered in /etc/groups)? Is the solution to just tell people "tough luck, you should register your group"?

I think requiring a name makes sense. Do you?

how will this affect containers on the system that are also using group blacklisting?

Hmm - that applies to your approach too right?

It really seems like this requires a new kernel functionality to make the most sense - a new per-process 'lockedgrp' from which groups can never be removed. Otherwise all we can really do in the parent container is say "never let the child container do a setgroup" (right? or have i confused myself on this topic once again?)

@cyphar
Copy link
Contributor Author

cyphar commented Mar 28, 2018

Alright, I'm going to work on the /etc/group configuration method in a separate branch and I'll make a separate PR once it's ready.

@hallyn
Copy link
Member

hallyn commented Mar 30, 2018

Thanks @cyphar - should I close this one then?

@cyphar
Copy link
Contributor Author

cyphar commented Mar 30, 2018

Yup, I'll close it.

@cyphar cyphar closed this Mar 30, 2018
@hallyn
Copy link
Member

hallyn commented Aug 29, 2023

Hey @cyphar did this topic continue elsewhere?

@hallyn hallyn reopened this Aug 29, 2023
@cyphar
Copy link
Contributor Author

cyphar commented Aug 30, 2023

No, I started working on it and ran into some roadbump and then moved on to something else. I can take a look at reviving this when I get back from vacation in ~2 weeks.

@hallyn
Copy link
Member

hallyn commented Nov 26, 2023

No, I started working on it and ran into some roadbump and then moved on to something else. I can take a look at reviving this when I get back from vacation in ~2 weeks.

ping? :)

@cyphar
Copy link
Contributor Author

cyphar commented Nov 27, 2023

😅 I will take a look at this again this week.

@cyphar
Copy link
Contributor Author

cyphar commented Nov 28, 2023

Okay, I've started rewriting at this again, and I've remembered the roadblock I ran into last time.

@hallyn How should I handle the fact that struct group comes from libc and thus we can't add fields to it? Adding an argument to pass back the flags would be a little invasive, should I just wrap struct group into struct group_ext (or similar)? Are there users of our internal libraries, or can we change stuff like this without issue?

@hallyn
Copy link
Member

hallyn commented Dec 27, 2023

Okay, I've started rewriting at this again, and I've remembered the roadblock I ran into last time.

@hallyn How should I handle the fact that struct group comes from libc and thus we can't add fields to it? Adding an argument to pass back the flags would be a little invasive, should I just wrap struct group into struct group_ext (or similar)? Are there users of our internal libraries, or can we change stuff like this without issue?

D'oh, sorry, I never replied to this...

There are no outside users of our internal libraries that I know of. Only libsubid. Introducing struct group_ext should be fine. I can't visualize right now where all you need this, so not quite sure whether there would be a better way. Could you post a link to your current branch so I can take a look?

@hallyn
Copy link
Member

hallyn commented Aug 2, 2024

Hi @cyphar - no pressure, just wondering - have you given this any more thought? :)

@alejandro-colomar
Copy link
Collaborator

This would need rebasing.

@cyphar
Copy link
Contributor Author

cyphar commented Dec 15, 2025

It needs a complete rework to be honest (rather than marking the mapping as denying setgroups, there should be a marking for a group to indicate it is "locked" and thus must not be dropped). Last I looked, I was worried about changing the structures we use for groups but @hallyn said it was fine, so it might be okay to just do it in the most straightforward way.

Unfortunately, this seems to keep falling off my plate...

Comment on lines +308 to +316
for (i = 0; NULL != list[i]; i++) {
int j = strlen(comma);
if (j > 0)
comma[j++] = ',';
strncat(comma, list[i], commalen - j);
}

return comma;
}
Copy link
Collaborator

@alejandro-colomar alejandro-colomar Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use stpecpy() instead. We have forbidden strncat() with string input; we only allow it with [[gnu::nonstring]] input --that is, utmp(5) members--.

With stpecpy() --which is defined in lib/string/--, it would be:

char  *p, *e;

[...]

p = comma;
e = comma + commalen + 1;
for (int i = 0; NULL != list[i]; i++) {
	p = stpecpy(p, e, list[i]);
	p = stpecpy(p, e, ",");
}

if (p == NULL) {
	free(comma);
	return NULL;
}

return comma;

Copy link
Collaborator

@alejandro-colomar alejandro-colomar Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, since we know there won't be truncation, we can just use stpcpy(3) from libc.

char  *p;

[...]

p = comma;
for (int i = 0; NULL != list[i]; i++) {
	p = stpecpy(p, list[i]);
	p = stpecpy(p, ",");
}

return comma;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants